#include "vsf.h"
#include "component/port/linux/vsflinux.h"

#include <pthread.h>

struct vsflinux_pthread_priv_t
{
	struct vsflinux_thread_t thread;

	void *param;
	void * (*entry)(void *param);
};

static void vsflinux_pthread_on_run(struct vsfsm_thread_t *thread)
{
	struct vsflinux_pthread_priv_t *priv = (struct vsflinux_pthread_priv_t *)thread->priv;
	priv->thread.retval = (uint32_t)priv->entry(priv->param);
}

extern void vsflinux_thread_on_terminate(struct vsfsm_thread_t *thread);
static const struct vsfsm_thread_op_t vsflinux_pthread_op =
{
	.on_run = vsflinux_pthread_on_run,
	.on_terminate = vsflinux_thread_on_terminate,
};

pthread_t pthread_self(void)
{
	struct vsfsm_thread_t *thread = vsfsm_thread_get_cur();
	return thread == NULL ? 0 : ((struct vsflinux_thread_t *)thread->priv)->tid;
}

int pthread_create(pthread_t *tidp, const pthread_attr_t *attr,
		void * (*start_rtn)(void *), void *arg)
{
	struct vsflinux_pthread_priv_t *priv;
	struct vsfsm_thread_t *thread = vsflinux_thread_new(
		sizeof(struct vsflinux_pthread_priv_t), 0, &vsflinux_pthread_op);
	if (!thread) return -1;

	priv = (struct vsflinux_pthread_priv_t *)thread->priv;
	priv->entry = start_rtn;
	priv->param = arg;

	*tidp = priv->thread.tid;
	vsfsm_thread_start(thread);

	return 0;
}

int pthread_join(pthread_t tid, void **retval)
{
	struct vsfsm_thread_t *thread = vsflinux_thread_get(tid);
	struct vsflinux_pthread_priv_t *priv = (struct vsflinux_pthread_priv_t *)thread->priv;
	if (thread != NULL)
	{
		priv->thread.pending = vsfsm_thread_get_cur();
		vsfsm_thread_wfe(VSFSM_EVT_USER);
		if (retval != NULL)
			*retval = (void *)priv->thread.retval;
	}
	return 0;
}

void pthread_exit(void *retval)
{
	struct vsfsm_thread_t *thread = vsfsm_thread_get_cur();
	struct vsflinux_pthread_priv_t *priv = (struct vsflinux_pthread_priv_t *)thread->priv;
	if (thread != NULL)
	{
		priv->thread.retval = (uint32_t)retval;
		thread->terminated = true;
		vsfsm_thread_ret();
	}
}

int pthread_cancel(pthread_t thread)
{
	// TODO: check cancelable
	return 0;
}

// todo: add signal for thread
int pthread_kill(pthread_t thread, int sig)
{
	return 0;
}

// mutex
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
{
	vsfsm_crit_init(mutex, VSFSM_EVT_USER);
	return 0;
}

int pthread_mutex_destroy(pthread_mutex_t *mutex)
{
	return pthread_mutex_init(mutex, NULL);
}

int pthread_mutex_lock(pthread_mutex_t *mutex)
{
	struct vsfsm_thread_t *thread = vsfsm_thread_get_cur();
	if (vsfsm_crit_enter(mutex, &thread->sm))
		vsfsm_thread_wfe(VSFSM_EVT_USER);
	return 0;
}

int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
	vsfsm_crit_leave(mutex);
	return 0;
}

// cond
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)
{
	cond->pending_next = NULL;
	return 0;
}

int pthread_cond_destroy(pthread_cond_t *cond)
{
	return 0;
}

int pthread_cond_signal(pthread_cond_t *cond)
{
	uint8_t origlevel = vsfsm_sched_lock();
	struct vsfsm_t *cur = cond->pending_next;
	if (cur)
	{
		struct vsfsm_t *next = cur->pending_next;
		vsfsm_post_evt_pending(cur, VSFSM_EVT_USER);
		cond->pending_next = next;
	}
	vsfsm_sched_unlock(origlevel);
	return 0;
}

int pthread_cond_broadcast(pthread_cond_t *cond)
{
	uint8_t origlevel = vsfsm_sched_lock();
	struct vsfsm_t *cur = cond->pending_next;
	while (cur)
	{
		struct vsfsm_t *next = cur->pending_next;
		vsfsm_post_evt_pending(cur, VSFSM_EVT_USER);
		cur = next;
	}
	cond->pending_next = NULL;
	vsfsm_sched_unlock(origlevel);
	return 0;
}

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
	struct vsfsm_thread_t *thread = vsfsm_thread_get_cur();
	uint8_t origlevel = vsfsm_sched_lock();
	thread->sm.pending_next = cond->pending_next;
	cond->pending_next = &thread->sm;
	vsfsm_sched_unlock(origlevel);

	pthread_mutex_unlock(mutex);
	vsfsm_thread_wfe(VSFSM_EVT_USER);
	return 0;
}

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
		const struct timespec *abstime)
{
	return pthread_cond_wait(cond, mutex);
}